package main

import (
	"errors"
	"fmt"
	"gitee.com/simple-set/simple.io/src/event"
	"gitee.com/simple-set/simple.io/src/process/codec"
	"gitee.com/simple-set/simple.io/src/process/http"
	"log"
	"regexp"
	"strings"
)

// Forwarded RFC 7239 https://datatracker.ietf.org/doc/html/rfc7239, 代理扩展规范
type Forwarded struct {
	For_  string // 客户端地址
	Host  string // 目标服务器地址
	By    string // 代理服务器地址
	Proto string // 协议
}

func NewForwarded() *Forwarded {
	return &Forwarded{}
}

func NewForwardedParse(str string) *Forwarded {
	forwarded := NewForwarded()
	for _, item := range strings.Split(str, ";") {
		name, value, found := strings.Cut(item, ":")
		name = strings.TrimSpace(name)
		value = strings.TrimSpace(value)
		if name == "" || value == "" || !found {
			continue
		}
		if name == "for" {
			forwarded.For_ = value
		} else if name == "host" {
			forwarded.Host = value
		} else if name == "proto" {
			forwarded.Proto = value
		} else if name == "by" {
			forwarded.By = value
		}
	}
	return forwarded
}

func NewForwardedAll(for_ string, host string, by string, proto string) *Forwarded {
	return &Forwarded{For_: for_, Host: host, By: by, Proto: proto}
}

func (f *Forwarded) AddBy(addr string) {
	if f.By == "" {
		f.By = addr
	} else {
		f.By = fmt.Sprintf("%s,%s", f.By, addr)
	}
}

func (f *Forwarded) ToString() string {
	return fmt.Sprintf("for=%s;by=%s;host=%s;proto=%s", f.For_, f.By, f.Host, f.Proto)
}

type HttpProxy struct {
	backend *event.BootClient
}

func NewHttpProxy() *HttpProxy {
	bootClient := event.NewBootClient()
	bootClient.Register(NewHttpBackend())
	return &HttpProxy{backend: bootClient}
}

func (h *HttpProxy) Accept(session *event.Session) error {
	return nil
}

func (h *HttpProxy) Disconnect(session *event.Session) error {
	return nil
}

func (h *HttpProxy) Active(session *event.Session, value any) (any, error) {
	request, ok := value.(*http.Request)
	if !ok {
		return nil, errors.New("invalid request")
	}
	if err := h.setForwarded(session, request); err != nil {
		return nil, err
	}

	if starterClient, err := h.getConnect(session, request); err != nil {
		return nil, err
	} else {
		return starterClient.Request(session)
	}
}

func (h *HttpProxy) getConnect(session *event.Session, request *http.Request) (event.StarterClient, error) {
	if connect, ok := session.GetAttr("backendServer").(event.StarterClient); ok {
		return connect, nil
	}

	var serverAddr string
	if regexp.MustCompile(`^[a-zA-Z0-9\-.]+:[0-9]+$`).MatchString(request.Host) {
		serverAddr = request.Host
	} else {
		serverAddr = fmt.Sprintf("%s:%s", request.Host, "80")
	}

	if connect, err := h.backend.DialTcp(serverAddr); err != nil {
		return nil, err
	} else {
		session.SetAttr("backendServer", connect)
		return connect, nil
	}
}

// RFC 7239 Forwarded
func (h *HttpProxy) setForwarded(session *event.Session, request *http.Request) error {
	if value := request.Header.Get("Forwarded"); value == "" {
		forwarded := NewForwarded()
		forwarded.For_ = session.Socket.RemoteAddr().String()
		forwarded.By = session.Socket.LocalAddr().String()
		forwarded.Host = request.Host
		forwarded.Proto = "http"
		request.Header.Add("Forwarded", forwarded.ToString())
	} else {
		forwarded := NewForwardedParse(value)
		forwarded.AddBy(session.Socket.LocalAddr().String())
		request.Header.Add("Forwarded", forwarded.ToString())
	}
	return nil
}

type HttpBackend struct {
	requestEncode   http.RequestEncode
	responseEncode  http.ResponseEncode
	responseDecoder http.ResponseDecoder
}

func NewHttpBackend() *HttpBackend {
	return &HttpBackend{
		requestEncode:   http.RequestEncode{},
		responseEncode:  http.ResponseEncode{},
		responseDecoder: http.ResponseDecoder{},
	}
}

func (h *HttpBackend) Accept(session *event.Session) error {
	return nil
}

func (h *HttpBackend) Disconnect(session *event.Session) error {
	return nil
}

func (h *HttpBackend) Active(backendSession *event.Session, value any) (any, error) {
	proxySession, ok := value.(*event.Session)
	if !ok {
		return nil, errors.New("invalid session")
	}
	request, ok := proxySession.GetAttr("request").(*http.Request)
	if !ok {
		return nil, errors.New("invalid request")
	}

	if request.Method == "CONNECT" {
		// 建立隧道,通知客户端http隧道建立成功
		request.Response.SetStatusCodeAndText(200, "Connection established")
		if err := h.responseEncode.Encode(request.Response); err != nil {
			return nil, err
		}
	} else {
		// 非建立隧道,直接转发请求到目标服务器
		request.BufWriter = codec.NewWriteByteBuf(backendSession.Socket.Writer)
		if err := h.requestEncode.Encoder(request); err != nil {
			return nil, err
		}
	}
	return h.transfer(backendSession, proxySession)
}

// 传输数据, 相互转发字节流, 并不解析数据
func (h *HttpBackend) transfer(backendSession *event.Session, proxySession *event.Session) (any, error) {
	go func() {
		buf := make([]byte, 2048)
		for {
			if readSize, readErr := backendSession.Socket.Reader.Read(buf); readErr != nil {
				proxySession.Close()
				log.Println("backendSession read err:", readErr)
				break
			} else if writeErr := proxySession.Socket.WriterBytes(buf[:readSize]); writeErr != nil {
				backendSession.Close()
				log.Println("proxySession write err:", readErr)
				break
			} else {
				log.Println("backendSession read size:", readSize)
			}
		}
	}()

	buf := make([]byte, 2048)
	for {
		if readSize, readErr := proxySession.Socket.Reader.Read(buf); readErr != nil {
			backendSession.Close()
			log.Println("proxySession read err:", readErr)
			return nil, readErr
		} else if writeErr := backendSession.Socket.WriterBytes(buf[:readSize]); writeErr != nil {
			proxySession.Close()
			log.Println("backendSession write err:", readErr)
			return nil, writeErr
		} else {
			log.Println("proxySession read size:", readSize)
		}
	}
}
